/*
 * Copyright (c) 2009, 2012 IBM Corp.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Dave Locke - initial API and implementation and/or initial documentation
 */
package org.eclipse.paho.client.mqttv3;

import java.util.Properties;

import javax.net.SocketFactory;

import org.eclipse.paho.client.mqttv3.util.Debug;

/**
 * Holds options that control how the client connects to a server.
 */
public class MqttConnectOptions {

    /**
     * The default keep alive interval in seconds if one is not specified
     */
    public static final int KEEP_ALIVE_INTERVAL_DEFAULT = 200;
    /**
     * The default connection timeout in seconds if one is not specified It must be little than yunba connect timeout： 10s
     */
    public static final int CONNECTION_TIMEOUT_DEFAULT = 8;
    /**
     * The default clean session setting if one is not specified
     */
    public static final boolean CLEAN_SESSION_DEFAULT = true;

    private int keepAliveInterval = KEEP_ALIVE_INTERVAL_DEFAULT;
    private String willDestination = null;
    private MqttMessage willMessage = null;
    private String userName;
    private char[] password;
    private SocketFactory socketFactory;
    private Properties sslClientProps = null;
    private boolean cleanSession = CLEAN_SESSION_DEFAULT;
    private int connectionTimeout = CONNECTION_TIMEOUT_DEFAULT;

    /**
     * Constructs a new <code>MqttConnectOptions</code> object using the default values.
     *
     * The defaults are:
     * <ul>
     * <li>The keepalive interval is 60 seconds</li>
     * <li>Clean Session is true</li>
     * <li>The message delivery retry interval is 15 seconds</li>
     * <li>The connection timeout period is 30 seconds</li>
     * <li>No Will message is set</li>
     * <li>A standard SocketFactory is used</li>
     * </ul>
     * More information about these values can be found in the setter methods.
     */
    public MqttConnectOptions() {
    }

    /**
     * Returns the password to use for the connection.
     *
     * @return the password to use for the connection.
     */
    public char[] getPassword() {
        return password;
    }

    /**
     * Sets the password to use for the connection.
     */
    public void setPassword(char[] password) {
        this.password = password;
    }

    /**
     * Returns the user name to use for the connection.
     *
     * @return the user name to use for the connection.
     */
    public String getUserName() {
        return userName;
    }

    /**
     * Sets the user name to use for the connection.
     *
     * @throws IllegalArgumentException if the user name is blank or only contains whitespace characters.
     */
    public void setUserName(String userName) {
        if ((userName != null) && (userName.trim().equals(""))) {
            throw new IllegalArgumentException();
        }
        this.userName = userName;
    }

    /**
     * Sets the "Last Will and Testament" (LWT) for the connection. In the event that this client unexpectedly loses its connection to the server, the server will publish a message
     * to itself using the supplied details.
     *
     * @param topic the topic to publish to.
     * @param payload the byte payload for the message.
     * @param qos the quality of service to publish the message at (0, 1 or 2).
     * @param retained whether or not the message should be retained.
     */
    public void setWill(MqttTopic topic, byte[] payload, int qos, boolean retained) {
        String topicS = topic.getName();
        validateWill(topicS, payload);
        this.setWill(topicS, new MqttMessage(payload), qos, retained);
    }

    /**
     * Sets the "Last Will and Testament" (LWT) for the connection. In the event that this client unexpectedly loses its connection to the server, the server will publish a message
     * to itself using the supplied details.
     *
     * @param topic the topic to publish to.
     * @param payload the byte payload for the message.
     * @param qos the quality of service to publish the message at (0, 1 or 2).
     * @param retained whether or not the message should be retained.
     */
    public void setWill(String topic, byte[] payload, int qos, boolean retained) {
        validateWill(topic, payload);
        this.setWill(topic, new MqttMessage(payload), qos, retained);
    }

    /**
     * Validates the will fields.
     */
    private void validateWill(String dest, Object payload) {
        if ((dest == null) || (payload == null)) {
            throw new IllegalArgumentException();
        }
        MqttAsyncClient.validateTopic(dest);
    }

    /**
     * Sets up the will information, based on the supplied parameters.
     */
    protected void setWill(String topic, MqttMessage msg, int qos, boolean retained) {
        willDestination = topic;
        willMessage = msg;
        willMessage.setQos(qos);
        willMessage.setRetained(retained);
        // Prevent any more changes to the will message
        willMessage.setMutable(false);
    }

    /**
     * Returns the "keep alive" interval.
     *
     * @see #setKeepAliveInterval(int)
     * @return the keep alive interval.
     */
    public int getKeepAliveInterval() {
        return keepAliveInterval;
    }

    /**
     * Sets the "keep alive" interval. This value, measured in seconds, defines the maximum time interval between messages sent or received. It enables the client to detect if the
     * server is no longer available, without having to wait for the TCP/IP timeout. The client will ensure that at least one message travels across the network within each keep
     * alive period. In the absence of a data-related message during the time period, the client sends a very small "ping" message, which the server will acknowledge. A value of 0
     * disables keepalive processing in the client.
     * <p>
     * The default value is 60 seconds</p>
     *
     * @param keepAliveInterval the interval, measured in seconds, must be >= 0.
     */
    public void setKeepAliveInterval(int keepAliveInterval) throws IllegalArgumentException {
        if (keepAliveInterval < 0) {
            throw new IllegalArgumentException();
        }
        this.keepAliveInterval = keepAliveInterval;
    }

    /**
     * Returns the connection timeout value.
     *
     * @see #setConnectionTimeout(int)
     * @return the connection timeout value.
     */
    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    /**
     * Sets the connection timeout value. This value, measured in seconds, defines the maximum time interval the client will wait for the network connection to the MQTT server to
     * be established. The default timeout is 30 seconds.
     *
     * @param connectionTimeout the timeout value, measured in seconds.
     */
    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    /**
     * Returns the socket factory that will be used when connecting, or <code>null</code> if one has not been set.
     */
    public SocketFactory getSocketFactory() {
        return socketFactory;
    }

    /**
     * Sets the <code>SocketFactory</code> to use. This allows an application to apply its own policies around the creation of network sockets. If using an SSL connection, an
     * <code>SSLSocketFactory</code> can be used to supply application-specific security settings.
     *
     * @param socketFactory the factory to use.
     */
    public void setSocketFactory(SocketFactory socketFactory) {
        this.socketFactory = socketFactory;
    }

    /**
     * Returns the topic to be used for last will and testament (LWT).
     *
     * @return the MqttTopic to use, or <code>null</code> if LWT is not set.
     * @see #setWill(MqttTopic, byte[], int, boolean)
     */
    public String getWillDestination() {
        return willDestination;
    }

    /**
     * Returns the message to be sent as last will and testament (LWT). The returned object is "read only". Calling any "setter" methods on the returned object will result in an
     * <code>IllegalStateException</code> being thrown.
     *
     * @return the message to use, or <code>null</code> if LWT is not set.
     */
    public MqttMessage getWillMessage() {
        return willMessage;
    }

    /**
     * Returns the SSL properties for the connection.
     *
     * @return the properties for the SSL connection
     */
    public Properties getSSLProperties() {
        return sslClientProps;
    }

    /**
     * Sets the SSL properties for the connection. Note that these properties are only valid if an implementation of the Java Secure Socket Extensions (JSSE) is available. These
     * properties are
     * <em>not</em> used if a SocketFactory has been set using {@link #setSocketFactory(SocketFactory)}. The following properties can be used:</p>
     * <dl>
     * <dt>com.ibm.ssl.protocol</dt>
     * <dd>One of: SSL, SSLv3, TLS, TLSv1, SSL_TLS.</dd>
     * <dt>com.ibm.ssl.contextProvider
     * <dd>Underlying JSSE provider. For example "IBMJSSE2" or "SunJSSE"</dd>
     *
     * <dt>com.ibm.ssl.keyStore</dt>
     * <dd>The name of the file that contains the KeyStore object that you want the KeyManager to use. For example /mydir/etc/key.p12</dd>
     *
     * <dt>com.ibm.ssl.keyStorePassword</dt>
     * <dd>The password for the KeyStore object that you want the KeyManager to use. The password can either be in plain-text, or may be obfuscated using the static method:
     * <code>com.ibm.micro.security.Password.obfuscate(char[] password)</code>. This obfuscates the password using a simple and insecure XOR and Base64 encoding mechanism. Note
     * that this is only a simple scrambler to obfuscate clear-text passwords.</dd>
     *
     * <dt>com.ibm.ssl.keyStoreType</dt>
     * <dd>Type of key store, for example "PKCS12", "JKS", or "JCEKS".</dd>
     *
     * <dt>com.ibm.ssl.keyStoreProvider</dt>
     * <dd>Key store provider, for example "IBMJCE" or "IBMJCEFIPS".</dd>
     *
     * <dt>com.ibm.ssl.trustStore</dt>
     * <dd>The name of the file that contains the KeyStore object that you want the TrustManager to use.</dd>
     *
     * <dt>com.ibm.ssl.trustStorePassword</dt>
     * <dd>The password for the TrustStore object that you want the TrustManager to use. The password can either be in plain-text, or may be obfuscated using the static method:
     * <code>com.ibm.micro.security.Password.obfuscate(char[] password)</code>. This obfuscates the password using a simple and insecure XOR and Base64 encoding mechanism. Note
     * that this is only a simple scrambler to obfuscate clear-text passwords.</dd>
     *
     * <dt>com.ibm.ssl.trustStoreType</dt>
     * <dd>The type of KeyStore object that you want the default TrustManager to use. Same possible values as "keyStoreType".</dd>
     *
     * <dt>com.ibm.ssl.trustStoreProvider</dt>
     * <dd>Trust store provider, for example "IBMJCE" or "IBMJCEFIPS".</dd>
     *
     * <dt>com.ibm.ssl.enabledCipherSuites</dt>
     * <dd>A list of which ciphers are enabled. Values are dependent on the provider, for example: SSL_RSA_WITH_AES_128_CBC_SHA;SSL_RSA_WITH_3DES_EDE_CBC_SHA.</dd>
     *
     * <dt>com.ibm.ssl.keyManager</dt>
     * <dd>Sets the algorithm that will be used to instantiate a KeyManagerFactory object instead of using the default algorithm available in the platform. Example values:
     * "IbmX509" or "IBMJ9X509".
     * </dd>
     *
     * <dt>com.ibm.ssl.trustManager</dt>
     * <dd>Sets the algorithm that will be used to instantiate a TrustManagerFactory object instead of using the default algorithm available in the platform. Example values: "PKIX"
     * or "IBMJ9X509".
     * </dd>
     * </dl>
     */
    public void setSSLProperties(Properties props) {
        this.sslClientProps = props;
    }

    /**
     * Returns whether the server should remember state for the client across reconnects.
     *
     * @return the clean session flag
     */
    public boolean isCleanSession() {
        return this.cleanSession;
    }

    /**
     * Sets whether the server should remember state for the client across reconnects. This includes subscriptions and the state of any in-flight messages.
     */
    public void setCleanSession(boolean cleanSession) {
        this.cleanSession = cleanSession;
    }

    public Properties getDebug() {
        Properties p = new Properties();
        p.put("CleanSession", new Boolean(isCleanSession()));
        p.put("ConTimeout", new Integer(getConnectionTimeout()));
        p.put("KeepAliveInterval", new Integer(getKeepAliveInterval()));
        p.put("UserName", (getUserName() == null) ? "null" : getUserName());
        p.put("WillDestintation", (getWillDestination() == null) ? "null" : getWillDestination());
        if (getSocketFactory() == null) {
            p.put("SocketFactory", "null");
        } else {
            p.put("SocketFactory", getSocketFactory());
        }
        if (getSSLProperties() == null) {
            p.put("SSLProperties", "null");
        } else {
            p.put("SSLProperties", getSSLProperties());
        }
        return p;
    }

    public String toString() {
        return Debug.dumpProperties(getDebug(), "Connection options");
    }
}
